Add more flexible _remote_change() API , expose via 'ostree remote'
authorColin Walters <walters@verbum.org>
Mon, 15 Dec 2014 21:21:15 +0000 (16:21 -0500)
committerColin Walters <walters@verbum.org>
Tue, 16 Dec 2014 02:28:09 +0000 (21:28 -0500)
For Anaconda, I needed OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS,
with the GFile *sysroot argument to avoid ugly hacks.  We want to
write the content provided via "ostreesetup" as a remote to the target
chroot only in the case where it isn't provided as part of the tree
content itself.

This is also potentially useful in idempotent systems management tools
like Ansible.

https://bugzilla.gnome.org/show_bug.cgi?id=741577

src/libostree/ostree-repo.c
src/libostree/ostree-repo.h
src/ostree/ot-builtin-remote.c
tests/test-remote-add.sh

index b627046b89e25a0e5a47f1929a6bd1395c2320a1..fb4ff91cbc21cf2d8507cf3fc640402f2eded355 100644 (file)
@@ -616,33 +616,16 @@ keyfile_set_from_vardict (GKeyFile     *keyfile,
     }
 }
 
-/**
- * ostree_repo_remote_add:
- * @self: Repo
- * @name: Name of remote
- * @url: URL for remote (if URL begins with metalink=, it will be used as such)
- * @options: (allow-none): GVariant of type a{sv}
- * @cancellable: Cancellable
- * @error: Error
- *
- * Create a new remote named @name pointing to @url.  If @options is
- * provided, then it will be mapped to #GKeyFile entries, where the
- * GVariant dictionary key is an option string, and the value is
- * mapped as follows:
- *   * s: g_key_file_set_string()
- *   * b: g_key_file_set_boolean()
- *   * as: g_key_file_set_string_list()
- *
- */
-gboolean
-ostree_repo_remote_add (OstreeRepo     *self,
-                        const char     *name,
-                        const char     *url,
-                        GVariant       *options,
-                        GCancellable   *cancellable,
-                        GError        **error)
+static gboolean
+impl_repo_remote_add (OstreeRepo     *self,
+                      GFile          *sysroot,
+                      gboolean        if_not_exists,
+                      const char     *name,
+                      const char     *url,
+                      GVariant       *options,
+                      GCancellable   *cancellable,
+                      GError        **error)
 {
-  gs_unref_object GFile *etc_ostree_remotes_d = g_file_new_for_path (SYSCONFDIR "/ostree/remotes.d");
   local_cleanup_remote OstreeRemote *remote = NULL;
   gboolean ret = FALSE;
 
@@ -659,8 +642,12 @@ ostree_repo_remote_add (OstreeRepo     *self,
     }
 
   remote = ost_repo_get_remote (self, name, NULL);
-
-  if (remote != NULL)
+  if (remote != NULL && if_not_exists)
+    {
+      ret = TRUE;
+      goto out;
+    }
+  else if (remote != NULL)
     {
       GFile *file;
 
@@ -680,9 +667,17 @@ ostree_repo_remote_add (OstreeRepo     *self,
   remote->name = g_strdup (name);
   remote->group = g_strdup_printf ("remote \"%s\"", name);
 
-  if (ostree_repo_is_system (self))
+  if (sysroot != NULL || ostree_repo_is_system (self))
     {
+      const char *sysconf_remotes = SYSCONFDIR "/ostree/remotes.d";
       gs_free char *basename = g_strconcat (name, ".conf", NULL);
+      gs_unref_object GFile *etc_ostree_remotes_d = NULL;
+
+      if (sysroot == NULL)
+        etc_ostree_remotes_d = g_file_new_for_path (sysconf_remotes);
+      else
+        etc_ostree_remotes_d = g_file_resolve_relative_path (sysroot, sysconf_remotes + 1);
+
       remote->file = g_file_get_child (etc_ostree_remotes_d, basename);
     }
 
@@ -727,21 +722,42 @@ ostree_repo_remote_add (OstreeRepo     *self,
 }
 
 /**
- * ostree_repo_remote_delete:
+ * ostree_repo_remote_add:
  * @self: Repo
  * @name: Name of remote
+ * @url: URL for remote (if URL begins with metalink=, it will be used as such)
+ * @options: (allow-none): GVariant of type a{sv}
  * @cancellable: Cancellable
  * @error: Error
  *
- * Delete the remote named @name.  It is an error if the provided
- * remote does not exist.
+ * Create a new remote named @name pointing to @url.  If @options is
+ * provided, then it will be mapped to #GKeyFile entries, where the
+ * GVariant dictionary key is an option string, and the value is
+ * mapped as follows:
+ *   * s: g_key_file_set_string()
+ *   * b: g_key_file_set_boolean()
+ *   * as: g_key_file_set_string_list()
  *
  */
 gboolean
-ostree_repo_remote_delete (OstreeRepo     *self,
-                           const char     *name,
-                           GCancellable   *cancellable,
-                           GError        **error)
+ostree_repo_remote_add (OstreeRepo     *self,
+                        const char     *name,
+                        const char     *url,
+                        GVariant       *options,
+                        GCancellable   *cancellable,
+                        GError        **error)
+{
+  return impl_repo_remote_add (self, NULL, FALSE, name, url, options,
+                               cancellable, error);
+}
+
+static gboolean
+impl_repo_remote_delete (OstreeRepo     *self,
+                         GFile          *sysroot,
+                         gboolean        if_exists,
+                         const char     *name,
+                         GCancellable   *cancellable,
+                         GError        **error)
 {
   local_cleanup_remote OstreeRemote *remote = NULL;
   gboolean ret = FALSE;
@@ -756,7 +772,17 @@ ostree_repo_remote_delete (OstreeRepo     *self,
       goto out;
     }
 
-  remote = ost_repo_get_remote (self, name, error);
+  if (if_exists)
+    {
+      remote = ost_repo_get_remote (self, name, NULL);
+      if (!remote)
+        {
+          ret = TRUE;
+          goto out;
+        }
+    }
+  else
+    remote = ost_repo_get_remote (self, name, error);
 
   if (remote == NULL)
     goto out;
@@ -789,6 +815,71 @@ ostree_repo_remote_delete (OstreeRepo     *self,
   return ret;
 }
 
+/**
+ * ostree_repo_remote_delete:
+ * @self: Repo
+ * @name: Name of remote
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Delete the remote named @name.  It is an error if the provided
+ * remote does not exist.
+ *
+ */
+gboolean
+ostree_repo_remote_delete (OstreeRepo     *self,
+                           const char     *name,
+                           GCancellable   *cancellable,
+                           GError        **error)
+{
+  return impl_repo_remote_delete (self, NULL, FALSE, name, cancellable, error);
+}
+
+/**
+ * ostree_repo_remote_change:
+ * @self: Repo
+ * @sysroot: (allow-none): System root
+ * @changeop: Operation to perform
+ * @name: Name of remote
+ * @url: URL for remote (if URL begins with metalink=, it will be used as such)
+ * @options: (allow-none): GVariant of type a{sv}
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * A combined function handling the equivalent of
+ * ostree_repo_remote_add(), ostree_repo_remote_delete(), with more
+ * options.
+ *
+ *
+ */
+gboolean
+ostree_repo_remote_change (OstreeRepo     *self,
+                           GFile          *sysroot,
+                           OstreeRepoRemoteChange changeop,
+                           const char     *name,
+                           const char     *url,
+                           GVariant       *options,
+                           GCancellable   *cancellable,
+                           GError        **error)
+{
+  switch (changeop)
+    {
+    case OSTREE_REPO_REMOTE_CHANGE_ADD:
+      return impl_repo_remote_add (self, sysroot, FALSE, name, url, options,
+                                   cancellable, error);
+    case OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS:
+      return impl_repo_remote_add (self, sysroot, TRUE, name, url, options,
+                                   cancellable, error);
+    case OSTREE_REPO_REMOTE_CHANGE_DELETE:
+      return impl_repo_remote_delete (self, sysroot, FALSE, name,
+                                      cancellable, error);
+    case OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS:
+      return impl_repo_remote_delete (self, sysroot, TRUE, name,
+                                      cancellable, error);
+    }
+  g_assert_not_reached ();
+}
+
 /**
  * ostree_repo_remote_get_url:
  * @self: Repo
index 8ba093031d88151d5f13d7d044be77b41ec85f84..7320f2c78b505b8aa9abb6a6f9002eec6fd478bd 100644 (file)
@@ -79,6 +79,22 @@ gboolean      ostree_repo_remote_delete (OstreeRepo     *self,
                                          GCancellable   *cancellable,
                                          GError        **error);
 
+typedef enum {
+  OSTREE_REPO_REMOTE_CHANGE_ADD,
+  OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS,
+  OSTREE_REPO_REMOTE_CHANGE_DELETE,
+  OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS
+} OstreeRepoRemoteChange;
+
+gboolean      ostree_repo_remote_change (OstreeRepo     *self,
+                                         GFile          *sysroot,
+                                         OstreeRepoRemoteChange changeop,
+                                         const char     *name,
+                                         const char     *url,
+                                         GVariant       *options,
+                                         GCancellable   *cancellable,
+                                         GError        **error);
+
 gboolean      ostree_repo_remote_get_url (OstreeRepo   *self,
                                           const char   *name,
                                           char        **out_url,
index b28dc5a611aa9b95e1749c5973e1277a898e7b86..fc7f93ecaa7bbb9ddeb44d06a87be6ffa03d971c 100644 (file)
@@ -55,10 +55,12 @@ parse_keyvalue (const char  *keyvalue,
 
 static char **opt_set;
 static gboolean opt_no_gpg_verify;
+static gboolean opt_if_not_exists;
 
 static GOptionEntry add_option_entries[] = {
   { "set", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_set, "Set config option KEY=VALUE for remote", "KEY=VALUE" },
   { "no-gpg-verify", 0, 0, G_OPTION_ARG_NONE, &opt_no_gpg_verify, "Disable GPG verification", NULL },
+  { "if-not-exists", 0, 0, G_OPTION_ARG_NONE, &opt_if_not_exists, "Do nothing if the provided remote exists", NULL },
   { NULL }
 };
 
@@ -124,17 +126,25 @@ ostree_remote_builtin_add (int argc, char **argv, GCancellable *cancellable, GEr
                            "gpg-verify",
                            g_variant_new_variant (g_variant_new_boolean (FALSE)));
 
-  ret = ostree_repo_remote_add (repo, remote_name, remote_url,
-                                g_variant_builder_end (optbuilder),
-                                cancellable, error);
+  if (!ostree_repo_remote_change (repo, NULL,
+                                  opt_if_not_exists ? OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS : 
+                                  OSTREE_REPO_REMOTE_CHANGE_ADD,
+                                  remote_name, remote_url,
+                                  g_variant_builder_end (optbuilder),
+                                  cancellable, error))
+    goto out;
 
+  ret = TRUE;
  out:
   g_option_context_free (context);
 
   return ret;
 }
 
+gboolean opt_if_exists = FALSE;
+
 static GOptionEntry delete_option_entries[] = {
+  { "if-exists", 0, 0, G_OPTION_ARG_NONE, &opt_if_exists, "Do nothing if the provided remote does not exist", NULL },
   { NULL }
 };
 
@@ -160,8 +170,14 @@ ostree_remote_builtin_delete (int argc, char **argv, GCancellable *cancellable,
 
   remote_name = argv[1];
 
-  ret = ostree_repo_remote_delete (repo, remote_name, cancellable, error);
+  if (!ostree_repo_remote_change (repo, NULL,
+                                  opt_if_exists ? OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS : 
+                                  OSTREE_REPO_REMOTE_CHANGE_DELETE,
+                                  remote_name, NULL, NULL,
+                                  cancellable, error))
+    goto out;
 
+  ret = TRUE;
  out:
   g_option_context_free (context);
 
index 65efdc1ebffd5ac514cbf78ed9c7ffe51a7d6b8e..5e1a0eede27d8a3cb38384250a456bf2d69639db 100755 (executable)
@@ -25,14 +25,26 @@ echo '1..3'
 
 setup_test_repository "bare"
 $OSTREE remote add origin http://example.com/ostree/gnome
-echo "ok remote add"
-assert_file_has_content $test_tmpdir/repo/config "example.com"
+$OSTREE remote show-url origin >/dev/null
 echo "ok config"
 
 $OSTREE remote add --no-gpg-verify another http://another.com/repo
-assert_file_has_content $test_tmpdir/repo/config "gpg-verify=false"
+$OSTREE remote show-url another >/dev/null
 echo "ok remote no gpg-verify"
 
+if $OSTREE remote add --no-gpg-verify another http://another.example.com/anotherrepo 2>err.txt; then
+    assert_not_reached "Adding duplicate remote unexpectedly succeeded"
+fi
+echo "ok"
+
+$OSTREE remote add --if-not-exists --no-gpg-verify another http://another.example.com/anotherrepo
+$OSTREE remote show-url another >/dev/null
+echo "ok"
+
+$OSTREE remote add --if-not-exists --no-gpg-verify another-noexist http://another-noexist.example.com/anotherrepo
+$OSTREE remote show-url another-noexist >/dev/null
+echo "ok"
+
 $OSTREE remote delete another
 echo "ok remote delete"
 
@@ -41,4 +53,18 @@ if $OSTREE remote delete nosuchremote 2>err.txt; then
 fi
 assert_file_has_content err.txt "error: "
 
+$OSTREE remote delete --if-exists nosuchremote
+echo "ok"
 
+if $OSTREE remote show-url nosuchremote 2>/dev/null; then
+    assert_not_reached "Deleting remote unexpectedly failed"
+fi
+echo "ok"
+
+$OSTREE remote delete --if-exists origin
+echo "ok"
+
+if $OSTREE remote show-url origin 2>/dev/null; then
+    assert_not_reached "Deleting remote unexpectedly failed"
+fi
+echo "ok"